/*
 * Copyright (C) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <string>
#include <memory>
#include <vector>
#include <node_api.h>

#include "log/serialport_log_wrapper.h"
#include "i_serialport_client.h"
#include "x_napi_tool.h"
#include "serial_async_callback.h"

#define NUMBER_JS_2_C(napi_v, type, dest)   dest = pxt->SwapJs2C##type(napi_v)
#define NUMBER_C_2_JS(pxt, type, n) pxt->SwapC2Js##type(n)

#define BOOLEAN_JS_2_C(napi_v, type, dest)   dest = pxt->SwapJs2C##type(napi_v)
#define BOOLEAN_C_2_JS(pxt, type, n) pxt->SwapC2Js##type(n)

namespace OHOS {
namespace SerialPort {
struct SetOptions_value_struct {
    std::string in0;
    int32_t in1;
    int32_t in2;
    int32_t in3;
    int32_t in4;
    int32_t out;
};

void SetOptions_execute(XNapiTool *pxt, void *data)
{
    SetOptions_value_struct *vio = (SetOptions_value_struct *)data;
    SerialOptions opts;
    opts.speeds = vio->in1;
    opts.bits = vio->in2;
    opts.events = vio->in3;
    opts.stops = vio->in4;
    vio->out = get_serial_client()->SetOptions(vio->in0, opts);
}

void SetOptions_complete(XNapiTool *pxt, void *data)
{
    SetOptions_value_struct *vio = (SetOptions_value_struct *)data;
    napi_value result = nullptr;
    result = NUMBER_C_2_JS(pxt, Int32, vio->out);
    {
        napi_value args[XNapiTool::ARGV_CNT] = {result, nullptr};
        pxt->FinishAsync(vio->out, args);
    }
    delete vio;
}

napi_value SetOptions_middle(napi_env env, napi_callback_info info)
{
    XNapiTool *pxt = std::make_unique<XNapiTool>(env, info).release();
    if (pxt->IsFailed()) {
        napi_value err = pxt->GetError();
        delete pxt;
        return err;
    }

    struct SetOptions_value_struct *vio = new SetOptions_value_struct();
    
    pxt->SwapJs2CUtf8(pxt->GetArgv(0), vio->in0);
    NUMBER_JS_2_C(pxt->GetArgv(1),Int32,vio->in1);
    NUMBER_JS_2_C(pxt->GetArgv(2),Int32,vio->in2);
    NUMBER_JS_2_C(pxt->GetArgv(3),Int32,vio->in3);
    NUMBER_JS_2_C(pxt->GetArgv(4),Int32,vio->in4);
    
    napi_value result = pxt->StartAsync(SetOptions_execute, vio, SetOptions_complete,
        pxt->GetArgc() == 6 ? pxt->GetArgv(5) : nullptr);

    if (pxt->IsFailed()) {
        result = pxt->GetError();
    }
    return result;
}

struct OpenSerial_value_struct {
    std::string in0;
    int32_t out;
};

void OpenSerial_execute(XNapiTool *pxt, void *data)
{
    OpenSerial_value_struct *vio = (OpenSerial_value_struct *)data;
    vio->out = get_serial_client()->OpenSerial(vio->in0);
}

void OpenSerial_complete(XNapiTool *pxt, void *data)
{
    OpenSerial_value_struct *vio = (OpenSerial_value_struct *)data;
    napi_value result = nullptr;
    result = NUMBER_C_2_JS(pxt, Int32, vio->out);
    {
        napi_value args[XNapiTool::ARGV_CNT] = {result, nullptr};
        pxt->FinishAsync(vio->out, args);
    }
    delete vio;
}
napi_value OpenSerial_middle(napi_env env, napi_callback_info info)
{
    XNapiTool *pxt = std::make_unique<XNapiTool>(env, info).release();
    if (pxt->IsFailed()) {
        napi_value err = pxt->GetError();
        delete pxt;
        return err;
    }

    struct OpenSerial_value_struct *vio = new OpenSerial_value_struct();
    pxt->SwapJs2CUtf8(pxt->GetArgv(0), vio->in0);
    napi_value result = pxt->StartAsync(OpenSerial_execute, vio, OpenSerial_complete,
        pxt->GetArgc() == 2 ? pxt->GetArgv(1) : nullptr);
    if (pxt->IsFailed()) {
        result = pxt->GetError();
    }
    return result;
}

struct CloseSerial_value_struct {
    std::string in0;
    int32_t out;
};

void CloseSerial_execute(XNapiTool *pxt, void *data)
{
    CloseSerial_value_struct *vio = (CloseSerial_value_struct *)data;
    vio->out = get_serial_client()->CloseSerial(vio->in0);
}

void CloseSerial_complete(XNapiTool *pxt, void *data)
{
    CloseSerial_value_struct *vio = (CloseSerial_value_struct *)data;
    napi_value result = nullptr;
    result = NUMBER_C_2_JS(pxt, Int32, vio->out);
    {
        napi_value args[XNapiTool::ARGV_CNT] = {result, nullptr};
        pxt->FinishAsync(vio->out, args);
    }

    delete vio;
}

napi_value CloseSerial_middle(napi_env env, napi_callback_info info)
{
    XNapiTool *pxt = std::make_unique<XNapiTool>(env, info).release();
    if (pxt->IsFailed()) {
        napi_value err = pxt->GetError();
        delete pxt;
        return err;
    }
    struct CloseSerial_value_struct *vio = new CloseSerial_value_struct();
    pxt->SwapJs2CUtf8(pxt->GetArgv(0), vio->in0);
    napi_value result = pxt->StartAsync(CloseSerial_execute, vio, CloseSerial_complete,
        pxt->GetArgc() == 2 ? pxt->GetArgv(1) : nullptr);

    if (pxt->IsFailed()) {
        result = pxt->GetError();
    }
    return result;
}
struct ClearBuffer_value_struct {
    std::string in0;
    uint32_t in1;
    int32_t out;
};

void ClearBuffer_execute(XNapiTool *pxt, void *data)
{
    ClearBuffer_value_struct *vio = (ClearBuffer_value_struct *)data;
    vio->out = get_serial_client()->ClearBuffer(vio->in0, vio->in1);
}

void ClearBuffer_complete(XNapiTool *pxt, void *data)
{
    ClearBuffer_value_struct *vio = (ClearBuffer_value_struct *)data;
    
    napi_value result = nullptr;
    result = NUMBER_C_2_JS(pxt, Int32, vio->out);
    
    {
        napi_value args[XNapiTool::ARGV_CNT] = {result, nullptr};
        pxt->FinishAsync(vio->out, args);
    }

    delete vio;
}

napi_value ClearBuffer_middle(napi_env env, napi_callback_info info)
{
    XNapiTool *pxt = std::make_unique<XNapiTool>(env, info).release();
    if (pxt->IsFailed()) {
        napi_value err = pxt->GetError();
        delete pxt;
        return err;
    }
    

    struct ClearBuffer_value_struct *vio = new ClearBuffer_value_struct();
    
    pxt->SwapJs2CUtf8(pxt->GetArgv(0), vio->in0);
    NUMBER_JS_2_C(pxt->GetArgv(1), Uint32,vio->in1);

    
    napi_value result = pxt->StartAsync(ClearBuffer_execute, vio, ClearBuffer_complete,
        pxt->GetArgc() == 3 ? pxt->GetArgv(2) : nullptr);

    if (pxt->IsFailed()) {
        result = pxt->GetError();
    }
    return result;
}
struct SendData_value_struct {
    std::string in0;
    std::vector<uint8_t> in1;
    int32_t out;
};

void SendData_execute(XNapiTool *pxt, void *data)
{
    SendData_value_struct *vio = (SendData_value_struct *)data;
    vio->out = get_serial_client()->SendData(vio->in0, vio->in1.data(), vio->in1.size());
}

void SendData_complete(XNapiTool *pxt, void *data)
{
    SendData_value_struct *vio = (SendData_value_struct *)data;
    napi_value result = nullptr;
    result = NUMBER_C_2_JS(pxt, Int32, vio->out);
    {
        napi_value args[XNapiTool::ARGV_CNT] = {result, nullptr};
        pxt->FinishAsync(vio->out, args);
    }

    delete vio;
}

napi_value SendData_middle(napi_env env, napi_callback_info info)
{
    XNapiTool *pxt = std::make_unique<XNapiTool>(env, info).release();
    if (pxt->IsFailed()) {
        napi_value err = pxt->GetError();
        delete pxt;
        return err;
    }

    struct SendData_value_struct *vio = new SendData_value_struct();
    pxt->SwapJs2CUtf8(pxt->GetArgv(0), vio->in0);
    pxt->SwapJs2CUint8Array(pxt->GetArgv(1), vio->in1);

    napi_value result = pxt->StartAsync(SendData_execute, vio, SendData_complete,
        pxt->GetArgc() == 3 ? pxt->GetArgv(2) : nullptr);

    if (pxt->IsFailed()) {
        result = pxt->GetError();
    }
    return result;
}

struct RecvData_value_struct {
    std::string in0;
    uint32_t in1;
	std::vector<uint8_t> outData;
	int32_t out;
};

void RecvData_execute(XNapiTool *pxt, void *data)
{
    RecvData_value_struct *vio = (RecvData_value_struct *)data;
    vio->out = get_serial_client()->RecvData(vio->in0, vio->outData, vio->in1);
}

void RecvData_complete(XNapiTool *pxt, void *data)
{
    RecvData_value_struct *vio = (RecvData_value_struct *)data;
    
    napi_value result = NUMBER_C_2_JS(pxt, Int32, vio->out);
	napi_value outData = pxt->SwapC2JsUint8Array(vio->outData.data(), vio->outData.size());
	
    {
        napi_value args[XNapiTool::ARGV_CNT] = {result, outData};
        pxt->FinishAsync(vio->out, args);
    }

    delete vio;
}

napi_value RecvData_middle(napi_env env, napi_callback_info info)
{
    XNapiTool *pxt = std::make_unique<XNapiTool>(env, info).release();
    if (pxt->IsFailed()) {
        napi_value err = pxt->GetError();
        delete pxt;
        return err;
    }

    struct RecvData_value_struct *vio = new RecvData_value_struct();
    
    pxt->SwapJs2CUtf8(pxt->GetArgv(0), vio->in0);
    NUMBER_JS_2_C(pxt->GetArgv(1),Uint32,vio->in1);
    
    napi_value result = pxt->StartAsync(RecvData_execute, vio, RecvData_complete,
        pxt->GetArgc() == 3 ? pxt->GetArgv(2) : nullptr);

    if (pxt->IsFailed()) {
        result = pxt->GetError();
    }
    return result;
}
struct Transmit_value_struct {
    std::string in0;
    std::vector<uint8_t> in1;
    uint32_t in2;
    std::vector<uint8_t> outData;
	int32_t out;
};

void Transmit_execute(XNapiTool *pxt, void *data)
{
    Transmit_value_struct *vio = (Transmit_value_struct *)data;
    vio->out = get_serial_client()->Transmit(vio->in0, vio->in1.data(), vio->in1.size(),
        vio->outData, vio->in2);
}

void Transmit_complete(XNapiTool *pxt, void *data)
{
    Transmit_value_struct *vio = (Transmit_value_struct *)data;
	
	napi_value result = NUMBER_C_2_JS(pxt, Int32, vio->out);
	napi_value outData = pxt->SwapC2JsUint8Array(vio->outData.data(), vio->outData.size());
	
    {
        napi_value args[XNapiTool::ARGV_CNT] = {result, outData};
        pxt->FinishAsync(vio->out, args);
    }

    delete vio;
}

napi_value Transmit_middle(napi_env env, napi_callback_info info)
{
    XNapiTool *pxt = std::make_unique<XNapiTool>(env, info).release();
    if (pxt->IsFailed()) {
        napi_value err = pxt->GetError();
        delete pxt;
        return err;
    }

    struct Transmit_value_struct *vio = new Transmit_value_struct();
    
    pxt->SwapJs2CUtf8(pxt->GetArgv(0), vio->in0);
    pxt->SwapJs2CUint8Array(pxt->GetArgv(1), vio->in1);
    NUMBER_JS_2_C(pxt->GetArgv(2),Uint32,vio->in2);
    
    napi_value result = pxt->StartAsync(Transmit_execute, vio, Transmit_complete, pxt->GetArgc() == 4 ? pxt->GetArgv(3) : nullptr);

    if (pxt->IsFailed()) {
        result = pxt->GetError();
    }
    return result;
}

struct on_value_struct {
    std::string eventName;
};

napi_value on_middle(napi_env env, napi_callback_info info)
{
    XNapiTool *pxt = std::make_unique<XNapiTool>(env, info).release();
    if (pxt->IsFailed()) {
        napi_value err = pxt->GetError();
        delete pxt;
        return err;
    }

    struct on_value_struct *vio = new on_value_struct();
    pxt->SwapJs2CUtf8(pxt->GetArgv(0), vio->eventName);
    if (vio->eventName.length() > 0) {
        int32_t ret = get_serial_client()->SetAsyncRead(vio->eventName, SERIAL_READ_FLAG_ON);
        if (ret == 0) {
            std::shared_ptr<SerialAsyncCallback> callback =  std::make_shared<SerialAsyncCallback>();
            callback->SetEnv(vio->eventName, env, pxt->GetArgv(1));
            get_serial_client()->RegisterAsyncCallback(vio->eventName, callback);
        }
    }

    napi_value result = pxt->UndefinedValue();

    delete vio;
    if (pxt->IsFailed()) {
        result = pxt->GetError();
    }
    delete pxt; // release
    return result;
}

struct off_value_struct {
    std::string eventName;
};

napi_value off_middle(napi_env env, napi_callback_info info)
{
    XNapiTool *pxt = std::make_unique<XNapiTool>(env, info).release();
    if (pxt->IsFailed()) {
        napi_value err = pxt->GetError();
        delete pxt;
        return err;
    }

    struct off_value_struct *vio = new off_value_struct();
    pxt->SwapJs2CUtf8(pxt->GetArgv(0), vio->eventName);
    if (vio->eventName.length() > 0) {
        get_serial_client()->SetAsyncRead(vio->eventName, 0);
        get_serial_client()->UnregisterAsyncCallback(vio->eventName);
    }
    napi_value result = pxt->UndefinedValue();

    delete vio;
    if (pxt->IsFailed()) {
        result = pxt->GetError();
    }
    delete pxt; // release
    return result;
}

struct setGPIODirection_value_struct {
    int32_t in0;
    bool in1;
    int32_t out;
};

void setGPIODirection_execute(XNapiTool *pxt, void *data)
{
    setGPIODirection_value_struct *vio = (setGPIODirection_value_struct *)data;
    vio->out = get_serial_client()->SetGPIODirection(vio->in0, vio->in1);
}

void setGPIODirection_complete(XNapiTool *pxt, void *data)
{
    setGPIODirection_value_struct *vio = (setGPIODirection_value_struct *)data;
    napi_value result = nullptr;
    result = NUMBER_C_2_JS(pxt, Int32, vio->out);
    napi_value args[XNapiTool::ARGV_CNT] = {result, nullptr};
    pxt->FinishAsync(vio->out, args);

    delete vio;
}

napi_value setGPIODirection_middle(napi_env env, napi_callback_info info)
{
    XNapiTool *pxt = std::make_unique<XNapiTool>(env, info).release();
    if (pxt->IsFailed()) {
        napi_value err = pxt->GetError();
        delete pxt;
        return err;
    }

    struct setGPIODirection_value_struct *vio = new setGPIODirection_value_struct();
    NUMBER_JS_2_C(pxt->GetArgv(0), Int32, vio->in0);
    BOOLEAN_JS_2_C(pxt->GetArgv(1), Bool, vio->in1);

    napi_value result = pxt->StartAsync(setGPIODirection_execute, vio, setGPIODirection_complete,
        pxt->GetArgc() == 3 ? pxt->GetArgv(2) : nullptr);
    if (pxt->IsFailed()) {
        result = pxt->GetError();
    }
    return result;
}

struct setGPIOValue_value_struct {
    int32_t in0;
    int32_t in1;
    int32_t out;
};

void setGPIOValue_execute(XNapiTool *pxt, void *data)
{
    setGPIOValue_value_struct *vio = (setGPIOValue_value_struct *)data;
    vio->out = get_serial_client()->SetGPIOValue(vio->in0, vio->in1);
}

void setGPIOValue_complete(XNapiTool *pxt, void *data)
{
    setGPIOValue_value_struct *vio = (setGPIOValue_value_struct *)data;
    napi_value result = nullptr;
    result = NUMBER_C_2_JS(pxt, Int32, vio->out);
    napi_value args[XNapiTool::ARGV_CNT] = {result, nullptr};
    pxt->FinishAsync(vio->out, args);

    delete vio;
}

napi_value setGPIOValue_middle(napi_env env, napi_callback_info info)
{
    XNapiTool *pxt = std::make_unique<XNapiTool>(env, info).release();
    if (pxt->IsFailed()) {
        napi_value err = pxt->GetError();
        delete pxt;
        return err;
    }

    struct setGPIOValue_value_struct *vio = new setGPIOValue_value_struct();
    NUMBER_JS_2_C(pxt->GetArgv(0), Int32, vio->in0);
    NUMBER_JS_2_C(pxt->GetArgv(1), Int32, vio->in1);

    napi_value result = pxt->StartAsync(setGPIOValue_execute, vio, setGPIOValue_complete,
        pxt->GetArgc() == 3 ? pxt->GetArgv(2) : nullptr);

    if (pxt->IsFailed()) {
        result = pxt->GetError();
    }
    return result;
}


struct getGPIOValue_value_struct {
    int32_t in0;
    int32_t out0;
    int32_t out;
};

void getGPIOValue_execute(XNapiTool *pxt, void *data)
{
    getGPIOValue_value_struct *vio = (getGPIOValue_value_struct *)data;
    vio->out = get_serial_client()->GetGPIOValue(vio->in0, vio->out0);
    SERIALPORT_LOGI("GpioPage GetGPIOValue value:%{public}d.", vio->out0);
}

void getGPIOValue_complete(XNapiTool *pxt, void *data)
{
    getGPIOValue_value_struct *vio = (getGPIOValue_value_struct *)data;
    napi_value result = nullptr;
    napi_value out0 = nullptr;
    out0 = NUMBER_C_2_JS(pxt, Int32, vio->out0);
    result = NUMBER_C_2_JS(pxt, Int32, vio->out);
    napi_value args[XNapiTool::ARGV_CNT] = {result, out0};
    pxt->FinishAsync(vio->out, args);

    delete vio;
}

napi_value getGPIOValue_middle(napi_env env, napi_callback_info info)
{
    XNapiTool *pxt = std::make_unique<XNapiTool>(env, info).release();
    if (pxt->IsFailed()) {
        napi_value err = pxt->GetError();
        delete pxt;
        SERIALPORT_LOGE("GpioPage getGPIOValue_middle failed.");
        return err;
    }

    struct getGPIOValue_value_struct *vio = new getGPIOValue_value_struct();
    NUMBER_JS_2_C(pxt->GetArgv(0),Int32,vio->in0);

    napi_value result = pxt->StartAsync(getGPIOValue_execute, vio, getGPIOValue_complete,
        pxt->GetArgc() == 2 ? pxt->GetArgv(1) : nullptr);
    if (pxt->IsFailed()) {
        result = pxt->GetError();
        SERIALPORT_LOGE("GpioPage getGPIOValue_middle  StartAsync failed.");
    }
    return result;
}

} // namespace SerialPort
} // namespace OHOS

static napi_value init(napi_env env, napi_value exports)
{
    std::shared_ptr<XNapiTool> pxt = std::make_shared<XNapiTool>(env, exports);
    pxt->DefineFunction("setOptions", OHOS::SerialPort::SetOptions_middle);
    pxt->DefineFunction("openSerial", OHOS::SerialPort::OpenSerial_middle);
    pxt->DefineFunction("closeSerial", OHOS::SerialPort::CloseSerial_middle);
    pxt->DefineFunction("clearBuffer", OHOS::SerialPort::ClearBuffer_middle);
    pxt->DefineFunction("sendData", OHOS::SerialPort::SendData_middle);
    pxt->DefineFunction("recvData", OHOS::SerialPort::RecvData_middle);
    pxt->DefineFunction("transmit", OHOS::SerialPort::Transmit_middle);
	pxt->DefineFunction("on", OHOS::SerialPort::on_middle);
    pxt->DefineFunction("off", OHOS::SerialPort::off_middle);

    pxt->DefineFunction("setGPIODirection", OHOS::SerialPort::setGPIODirection_middle);
    pxt->DefineFunction("setGPIOValue", OHOS::SerialPort::setGPIOValue_middle);
    pxt->DefineFunction("getGPIOValue", OHOS::SerialPort::getGPIOValue_middle);

    return exports;
}

static napi_module g_serialHelper_Module = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = init,
    .nm_modname = "serialhelper",
    .nm_priv = ((void *)0),
    .reserved = {(void *)0},
};

extern "C" __attribute__((constructor)) void Register_serialHelper_Module(void)
{
    napi_module_register(&g_serialHelper_Module);
}
